/*
 * MIT License
 *
 * Copyright (c) 2020 wen.gu <454727014@qq.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

/***************************************************************************
 * Name: proxy.h
 *
 * Purpose: proxy base class
 *
 * Developer:
 *   wen.gu , 2021-05-16
 *
 * TODO:
 *
 ***************************************************************************/

/******************************************************************************
 **    INCLUDES
 ******************************************************************************/
#ifndef __PROXY_REQUESTERE_H__
#define __PROXY_REQUESTERE_H__

#include <functional>
#include <string>
#include <vector>
#include <future>

#include "deserializer.h"
#include "serializer.h"
#include "internal_impl.h"
#include "message.h"

#include "method_return.h"

/******************************************************************************
 **    MACROS
 ******************************************************************************/
/** for sync connect operation, wait forever untill connect to service */
#define CONNECT_WAIT_INFINITE (-1)
/******************************************************************************
 **    TYPE DEFINITIONS
 ******************************************************************************/

class ProxyRequester;


/** for proxy side */
/** for message receive ,
 * parameter: [1]: method Name, [2]: response information data
 *  
 **/
using ProxyReceiveHandler = std::function<void(MessagePtr)>;


using ProxyRequesterPtr = std::shared_ptr<ProxyRequester>;


enum class ConnectStatus: uint8_t
{
    kConnected = 0,
    kDisConnected,
};

/******************************************************************************
 **    CLASSES/FUNCTIONS DEFINITIONS
 ******************************************************************************/
class ProxyRequester
{
public:
    //class ServiceHandle;
    //using ServiceHandlePtr = std::shared_ptr<ServiceHandle>;
    //using ServiceHandleArray = std::vector<ServiceHandlePtr>;
   // using FindServiceHandler = std::function<void(ServiceHandleArray&)>;

    using ConnectStatusHandler = std::function<void(ConnectStatus)>;

    template<typename ReturnType>
    using ProxyReplyHandler = std::function<void(ReturnType)>
public:  
    ProxyRequester(const std::string& srv_name); 
    virtual ~ProxyRequester();
public:
    /** connect to service*/
    /**
     * start to connect to service and wait unitil timeout or successfull connect to service
     */
    virtual ErrorCode Connect(int32_t timeout_ms = CONNECT_WAIT_INFINITE) = 0;

    /**
     * try to connect to service, and return result immediately.
    */
    virtual ErrorCode TryConnect() = 0;

    /**
     * start to connect to service, the result will return by callback  'handler'
     */
    virtual ErrorCode ConnectAsync(ConnectStatusHandler handler) = 0;

    /**
     * cancel connet  when Connect method called and in waiting state. 
     */
    virtual void CancelConnect();
    /** disconnect from service*/
    virtual ErrorCode Disconnect() = 0;
public:
    static ProxyRequesterPtr Create(ComProtocolType type, const std::string& service_name);
public:
    virtual DeserializerPtr GetDeserializer(Message::PayloadPtr payload) = 0;
    virtual SerializerPtr GetSerializer() = 0;
    virtual MessagePtr MakeMessage(MessageType type) = 0;
    /** message id or session id */
    virtual bool RegisterReceiveHandler(MessageType type, MessageId id, ProxyReceiveHandler handler) = 0;
    virtual bool UnregisterReceiveHandler(MessageType type, MessageId id) = 0;
    virtual bool Send(MessagePtr msg) = 0;
public:
    virtual bool Subscribe(MessageId id) = 0;
    virtual bool Unsubscribe(MessageId id) = 0;
    virtual bool UnSubscribeAll() = 0;
    virtual bool SubscribeGroup(GroupId group_id) = 0; //this could be call EventBase::group_id() to get the group id
    virtual bool UnSubscribeGroup(GroupId group_id) = 0;
public:

    template<typename... Args>
    MessagePtr SerializeArguments(MessageType type, MessageId id, Args... args)
    {
        SerializerPtr ser = GetSerializer();
        if (!ser)
        {
            return nullptr;
        }

        if (!ser->Serialize(std::forward<Args>(args)...))
        {
            return nullptr;
        }

        MessagePtr msg = MakeMessage(type);
        if (msg)
        {
            msg->set_payload(ser->GetPayload());
            msg->set_message_id(id);
        }
        
        return msg;
    }

    template<typename R>
    bool DeserializeReturnValue(MessagePtr ret_msg, R& val)
    {
        DeserializerPtr deser = GetDeserializer(ret_msg->payload());

        if (!deser)
        {
            return false;
        }

        if (!deser->Deserialize(std::forward<R>(val)))
        {
            return false;
        }     

        return ErrorCode::OK;   
    }

    template<typename ReturnType>
    bool SetReplyHandler(SessionId id, ProxyReplyHandler<ReturnType&> handler)
    {
        return RegisterReplyHandler(id, [handler](MessagePtr msg)
        {
            ReturnType rt;          
            if (DeserializeReturnValue(msg, std::forward<ReturnType>(rt)))
            {
                handler(rt); 
            }    
            /** todo, refine me??  handler add status or error param. e.g handler(status, rt) */       
        });
    }

    template<typename ReturnType, typename ...Args>
    Return<ReturnType> MethodCall(MessageType type, MessageId id, Args... args)
    {
        MessagePtr msg = SerializeArguments(type, id, std::forward<Args>(args)...);
       
        if (!Send(msg))
        {
            std::future<ReturnType> tmp;
            return std::move(Return<ReturnType>(std::move(tmp), -1, nullptr));
        }

        std::shared_ptr<std::promise<ReturnType>> prm = std::make_shared<std::promise<ReturnType>>();

        std::future<ReturnType> fu = promise->get_future();


        if (!SetReplyHandler<ReturnType>(msg->session_id(), [promise](ReturnType& val)
        {
            promise->set_value(std::move(val));
        }))
        {
            std::future<ReturnType> tmp;
            return std::move(Return<ReturnType>(std::move(tmp), -1, nullptr));
        }

        return std::move(Return<ReturnType>(std::move(fu), msg->session_id(), [this](SessionId id,  ProxyReplyHandler<ReturnType&> handler) -> bool
        {
            return this->SetReplyHandler(id, handler);
        }));
    }


protected:
    virtual bool RegisterReplyHandler(SessionId id, ProxyReceiveHandler handler) = 0;

protected:
    std::string service_name_;    
};

#endif /** !__PROXY_REQUESTERE_H__ */

